package jmine.tec.services.execution;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import jmine.tec.executionlog.ExecutionLogController;
import jmine.tec.executionlog.domain.enumx.ExecutionItemStatus;
import jmine.tec.services.ServicesController;
import jmine.tec.services.api.Service;
import jmine.tec.services.api.ServiceAssertionFailedError;
import jmine.tec.services.api.ServiceExecutionException;
import jmine.tec.services.api.ServiceValidationExecutionException;
import jmine.tec.services.api.ServicesAPIMessages;
import jmine.tec.services.api.ServicesPage;
import jmine.tec.services.api.execution.ExecutorParams;
import jmine.tec.services.api.execution.ExecutorTransactionPolicy;
import jmine.tec.services.api.execution.ServicesPageExecutor;
import jmine.tec.services.io.ServiceBean;
import jmine.tec.utils.debug.TimerGroup;
import jmine.tec.utils.debug.impl.DebugRuntimeFactory;

import org.apache.log4j.Logger;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.transaction.TransactionDefinition;

import bancosys.tec.persist.validator.ValidationException;
import bancosys.tec.rtm.impl.RtmController;

/**
 * Implementation of the ServicesPageExecutor. It treats each ServicesPage independently. Errors or invalid services in one page will not
 * affect other pages. Each ServicesPage is executed inside a separate transaction, any exception thrown by any service will cause a
 * rollback. Validations also run inside a transaction that will be rolled back, never committed. Each page is validated, if no invalid
 * service is found, then it is executed. Errors during page execution will cause a rollback of that page. The error log of the services is
 * delegated to the ServiceErrorHandler, all other services will be logged by the executor: successful services, services not executed due
 * to previous service error or invalid service on the same page, and invalid services. If the ServiceErrorHandler generates a
 * ExecutionLogItem, then all services will have one ExecutionLogItem, generating a complete ExecutionLog. If the ExecutionLogItem's status
 * is 'OK', then all went fine. Even if the service executed successfully, but it's page then suffered a rollback due to a failure, it will
 * be logged as NOT_OK, because it's effect was rolled back.
 * 
 * @author lundberg
 */
public class BaseServicesPageExecutor implements ServicesPageExecutor {

    public static final String SERVICES_TIMER_IDENTIFIER = "Services Timer";

    public static final String MSG_NOT_EXECUTED = "Não executado pois outros serviços no mesmo bloco são inválidos.";

    public static final String MSG_ROLLEDBACK = "Serviço ok mas não executado pois outros serviços no mesmo bloco são inválidos.";

    private static final Logger LOG = Logger.getLogger(BaseServicesPageExecutor.class);

    private ExecutionLogController executionLogController;

    private ServicesController servicesController;

    private RtmController rtmController;

    /**
     * {@inheritDoc}
     */
    public void execute(List<ServicesPage> servicesPages) throws ServiceExecutionException {
        this.execute(servicesPages, new ExecutorParams());
    }

    /**
     * {@inheritDoc}
     */
    public void execute(List<ServicesPage> servicesPages, ExecutorParams params) throws ServiceExecutionException {
        List<Throwable> errors = new ArrayList<Throwable>();
        try {
            if (this.executionLogController.isExecuting()) {
                this.executionLogController.setTotalItemsInExecution(this.countItems(servicesPages));
            }
            List<List<Service<?>>> serviceBlocks = this.getServiceBlocks(servicesPages, params.getTransactionPolicy());
            try {
                if (params.getValidationPolicy().isBeforeAnyExecution()) {
                    this.validateAll(serviceBlocks, params);
                }
                for (List<Service<?>> services : serviceBlocks) {
                    if (params.getValidationPolicy().isBeforeBlockExecution()) {
                        SortedMap<Integer, ValidationOutcome> validationOutcome = this.validateServices(services, params);
                        if (!validationOutcome.isEmpty()) {
                            this.logInvalidServices(services, validationOutcome);
                            errors.add(this.createValidationException(services, validationOutcome));
                            continue;
                        }
                    }
                    SortedMap<Integer, Throwable> serviceErrors = this.executeServices(services, params);
                    if (!serviceErrors.isEmpty()) {
                        errors.addAll(serviceErrors.values());
                        if (this.isAbortExecution(services, serviceErrors, params)) {
                            break;
                        }
                    }
                }
            } catch (ServiceExecutionException e) {
                errors.add(e);
            }
        } catch (Throwable t) {
            if (DebugRuntimeFactory.ENABLED) {
                DebugRuntimeFactory.getInstance().getOrCreateMessageStore("servicesPageExecutor").postStackTrace(t);
            }
            throw new ServiceExecutionException(ServicesAPIMessages.EXECUTION_UNEXPECTED_ERROR.create(), t);
        }
        this.assertExecutionOk(errors);
    }

    /**
     * Cria a exception de validação de um bloco de serviços
     * 
     * @param services services
     * @param outcome outcome
     * @return Throwable
     */
    private Throwable createValidationException(List<Service<?>> services, SortedMap<Integer, ValidationOutcome> outcome) {
        StringBuilder validationMsg = new StringBuilder();
        for (int i = 0; i < services.size(); i++) {
            if (outcome.containsKey(i)) {
                ValidationOutcome error = outcome.get(i);
                validationMsg.append("\n");
                validationMsg.append(error.getError());
                if (error.getException() != null) {
                    StringWriter writer = new StringWriter();
                    PrintWriter printWriter = new PrintWriter(writer);
                    error.getException().printStackTrace(printWriter);
                    printWriter.flush();
                    validationMsg.append(writer.toString());
                    validationMsg.append("\n");
                    printWriter.close();
                }
            }
        }
        return new ServiceExecutionException(ServicesAPIMessages.VALIDATION_VALIDATION_ERROR.create(validationMsg));
    }

    /**
     * Método que checa se algum erro ocorreu, e em caso afirmativo gera uma exceção contendo a descrição dos erros.
     * 
     * @param errors os erros encontrados.
     * @throws ServiceExecutionException caso ocorra algum erro tenha cocorrido.
     */
    private void assertExecutionOk(List<Throwable> errors) throws ServiceExecutionException {
        if (errors != null && !errors.isEmpty()) {
            StringBuilder sb = new StringBuilder();
            for (Throwable t : errors) {
                this.rtmController.monitor(t);
                sb.append(t.getMessage());
            }
            throw new ServiceExecutionException(ServicesAPIMessages.EXECUTION_ERROR_PROCESSING_SERVICES.create(sb), errors);
        }
    }

    /**
     * Retorna o número de serviços na lista de páginas.
     * 
     * @param servicesPages servicesPages
     * @return número de serviços
     */
    protected final int countItems(List<ServicesPage> servicesPages) {
        int i = 0;
        for (ServicesPage page : servicesPages) {
            i += page.getServices().size();
        }
        return i;
    }

    /**
     * Divide os processos em blocos de acordo com a definição de transação de cada serviço. Essa definição nos serviços é feita através da
     * anotação <code>Transactional</code>.
     * 
     * @param pages as páginas de serviços.
     * @param policy policy
     * @return os serviços divididos em blocos.
     */
    public List<List<Service<?>>> getServiceBlocks(List<ServicesPage> pages, ExecutorTransactionPolicy policy) {
        List<List<Service<?>>> blocks = new ArrayList<List<Service<?>>>();

        List<Service<?>> current = new ArrayList<Service<?>>();
        blocks.add(current);
        for (ServicesPage page : pages) {
            for (Service<?> service : page.getServices()) {
                if (policy.isPerService() || policy.isWhenNeeded() && this.needsNewTransaction(service)) {
                    current = new ArrayList<Service<?>>();
                    blocks.add(current);
                    current.add(service);
                    current = new ArrayList<Service<?>>();
                    blocks.add(current);
                } else {
                    current.add(service);
                }
            }
            if (policy.isPerPage() && !current.isEmpty()) {
                current = new ArrayList<Service<?>>();
                blocks.add(current);
            }
        }

        return blocks;
    }

    /**
     * Testa se um serviço precisa de uma nova transação. A definição nos serviços é feita através da anotação <code>Transactional</code>.
     * 
     * @param service o serviço.
     * @return <code>true</code> caso uma nova transação seja necessária, <code>false</code> caso contrário.
     */
    protected boolean needsNewTransaction(Service<?> service) {
        TransactionDefinition def = service.getTransactionDefinition();
        return def != null && def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW;
    }

    /**
     * Validates a set of services, creating the necessary log items.
     * 
     * @param serviceBlocks list of services blocks
     * @param params {@link ExecutorParams}
     * @throws ServiceExecutionException if there invalid services exist.
     */
    protected void validateAll(List<List<Service<?>>> serviceBlocks, ExecutorParams params) throws ServiceExecutionException {
        Map<Integer, SortedMap<Integer, ValidationOutcome>> outcomes = new HashMap<Integer, SortedMap<Integer, ValidationOutcome>>();
        for (int i = 0; i < serviceBlocks.size(); i++) {
            SortedMap<Integer, ValidationOutcome> outcome = this.validateServices(serviceBlocks.get(i), params);
            if (!outcome.isEmpty()) {
                outcomes.put(i, outcome);
            }
        }
        if (!outcomes.isEmpty()) {
            StringBuilder validationMsg = new StringBuilder();
            for (int i = 0; i < serviceBlocks.size(); i++) {
                List<Service<?>> services = serviceBlocks.get(i);
                for (int j = 0; j < services.size(); j++) {
                    if (outcomes.containsKey(i) && outcomes.get(i).containsKey(j)) {
                        ValidationOutcome error = outcomes.get(i).get(j);
                        validationMsg.append("\n");
                        validationMsg.append(error.getError());
                        if (error.getException() != null) {
                            StringWriter writer = new StringWriter();
                            PrintWriter printWriter = new PrintWriter(writer);
                            error.getException().printStackTrace(printWriter);
                            printWriter.flush();
                            validationMsg.append(writer.toString());
                            validationMsg.append("\n");
                            printWriter.close();
                        }
                        this.logErrorExecution(services.get(j), error.getError(), error.getException());
                    } else {
                        this.logNotExecuted(services.get(j), MSG_NOT_EXECUTED);
                    }
                }
            }
            throw new ServiceExecutionException(ServicesAPIMessages.VALIDATION_VALIDATION_ERROR.create(validationMsg));
        }
    }

    /**
     * Validates a service block and returns the validation outcome
     * 
     * @param services services
     * @param params {@link ExecutorParams}
     * @return outcome
     * @throws ServiceExecutionException ServiceExecutionException
     */
    protected SortedMap<Integer, ValidationOutcome> validateServices(List<Service<?>> services, ExecutorParams params)
            throws ServiceExecutionException {
        Map<Service<?>, String> errors = new HashMap<Service<?>, String>();
        Map<Service<?>, Throwable> errorStackTraces = new HashMap<Service<?>, Throwable>();
        try {
            params.getTransactionPolicy().startTransaction(this.executionLogController);
            for (Service<?> service : services) {
                try {
                    List<String> serviceValidationErrors = this.getServicesController().getServiceValidator().validate(service);
                    if (serviceValidationErrors != null && !serviceValidationErrors.isEmpty()) {
                        errors.put(service, this.createValidationErrorMessage(service, serviceValidationErrors));
                    }
                } catch (Throwable t) {
                    LOG.error("Erro inesperado durante a validação do serviço " + service.toString() + ": " + t.getMessage(), t);
                    errors.put(service, "Erro inesperado durante a validação do serviço " + service.toString() + ": " + t.getMessage());
                    errorStackTraces.put(service, t);
                }
            }
        } finally {
            if (errors.isEmpty()) {
                params.getTransactionPolicy().commitTransaction(this.executionLogController);
            } else {
                params.getTransactionPolicy().rollbackTransaction(this.executionLogController);
            }
        }
        if (errors.isEmpty()) {
            return new TreeMap<Integer, ValidationOutcome>();
        } else {
            SortedMap<Integer, ValidationOutcome> outcome = new TreeMap<Integer, ValidationOutcome>();
            for (int i = 0; i < services.size(); i++) {
                Service<?> service = services.get(i);
                if (errors.containsKey(service)) {
                    outcome.put(i, new ValidationOutcome(errors.get(service), errorStackTraces.get(service)));
                }
            }
            return outcome;
        }
    }

    /**
     * Creates a message with the validation errors.
     * 
     * @param service service
     * @param serviceValidationErrors serviceValidationErrors
     * @return message.
     */
    private String createValidationErrorMessage(Service<?> service, List<String> serviceValidationErrors) {
        StringBuilder builder = new StringBuilder();
        String separator = "";

        for (String msg : serviceValidationErrors) {
            builder.append(separator);
            builder.append(msg);
            separator = "\n";
        }
        return builder.toString();
    }

    /**
     * Executes a services collection.<br>
     * <br>
     * If an exception is thrown during a service execution, all following services will not be executed, and will be logged as failures.
     * Any successful executed services will be rollback'd, unless it's a validation service.<br>
     * <br>
     * If no exceptions are thrown during services execution, all services will be logged as successful executions.
     * 
     * @param services the services.
     * @param params {@link ExecutorParams}
     * @return outcome sorted map of errors occurred while executing the services.
     * @throws ServiceExecutionException if there is any error unexpected errors during service execution
     */
    protected SortedMap<Integer, Throwable> executeServices(List<Service<?>> services, ExecutorParams params)
            throws ServiceExecutionException {
        params.getTransactionPolicy().startTransaction(this.executionLogController);
        try {
            SortedMap<Integer, Throwable> outcome = this.doExecuteServices(services);
            this.logServiceBlockExecution(services, outcome);
            if (this.rollbackNeeded(services, outcome)) {
                params.getTransactionPolicy().rollbackTransaction(this.executionLogController);
            } else {
                params.getTransactionPolicy().commitTransaction(this.executionLogController);
            }
            return outcome;
        } catch (Throwable t) {
            params.getTransactionPolicy().rollbackTransaction(this.executionLogController);
            throw new ServiceExecutionException(ServicesAPIMessages.EXECUTION_UNEXPECTED_ERROR.create(), t);
        }
    }

    /**
     * Logs a list of services that have at least 1 invalid service
     * 
     * @param services services
     * @param outcome outcome
     */
    protected void logInvalidServices(List<Service<?>> services, SortedMap<Integer, ValidationOutcome> outcome) {
        if (!this.executionLogController.isExecuting()) {
            return;
        }
        for (int i = 0; i < services.size(); i++) {
            Service<?> service = services.get(i);
            if (outcome.containsKey(i)) {
                ValidationOutcome error = outcome.get(i);
                this.logErrorExecution(service, error.getError(), error.getException());
            } else {
                this.logNotExecuted(service, MSG_NOT_EXECUTED);
            }
        }
    }

    /**
     * Logs the result of the execution of a service list based on the outcome map
     * 
     * @param services services
     * @param outcome outcome
     */
    protected void logServiceBlockExecution(List<Service<?>> services, SortedMap<Integer, Throwable> outcome) {
        if (!this.executionLogController.isExecuting()) {
            return;
        }
        if (this.rollbackNeeded(services, outcome)) {
            this.logRollbackExecuted(services, outcome);
            this.logNotExecuted(services, outcome);
        } else {
            this.logExecuted(services, outcome);
        }
    }

    /**
     * Logs all service executions before (and including) the failed service. Rollbacks all successful ones.
     * 
     * @param services the services.
     * @param outcome failedService
     */
    protected void logRollbackExecuted(List<Service<?>> services, SortedMap<Integer, Throwable> outcome) {
        if (this.executionLogController.isExecuting()) {
            int last = outcome.lastKey();
            for (int i = 0; i < services.size() && i <= last; i++) {
                Service<?> service = services.get(i);
                if (!outcome.containsKey(i)) {
                    this.logRollbackSuccessfulExecution(service);
                } else {
                    this.logErrorExecution(service, outcome.get(i).getMessage(), outcome.get(i));
                }
            }
        }
    }

    /**
     * Logs services that were not executed due to a previous error
     * 
     * @param services the services.
     * @param outcome failedService
     */
    protected final void logNotExecuted(List<Service<?>> services, SortedMap<Integer, ?> outcome) {
        if (this.executionLogController.isExecuting()) {
            if (outcome.isEmpty()) {
                throw new IllegalStateException("[BUG] Tentando marcar servicos como nao executado, mas nenhum erro encontrado no bloco!");
            }
            int lastExecuted = outcome.lastKey();
            for (int i = lastExecuted + 1; i < services.size(); i++) {
                this.logNotExecuted(services.get(i), MSG_NOT_EXECUTED);
            }
        }
    }

    /**
     * Logs all services, which were all executed. There might have some errors which didn't need to rollback all previous transactions;
     * those errors are logged at this point.
     * 
     * @param services list of services.
     * @param outcome outcome map of errors.
     */
    private void logExecuted(List<Service<?>> services, SortedMap<Integer, Throwable> outcome) {
        ListIterator<Service<?>> it = services.listIterator();
        while (it.hasNext()) {
            if (outcome.containsKey(it.nextIndex())) {
                Throwable exception = outcome.get(it.nextIndex());
                this.logErrorExecution(it.next(), exception.getMessage(), exception);
            } else {
                this.logSuccessfulExecution(it.next());
            }
        }
    }

    /**
     * Checks if a rollback is needed based on the outcome map
     * 
     * @param services services
     * @param outcome outcome
     * @return boolean
     */
    protected boolean rollbackNeeded(List<Service<?>> services, SortedMap<Integer, ?> outcome) {
        return this.verifyValidationServiceFail(services, outcome);
    }

    /**
     * Verifies given an outcome map of errors, if any of them happened while executing services which are not validation services.
     * 
     * @param services a list of services.
     * @param outcome map of services to errors.
     * @return <code>true</code> if all errors aren't from validation service, <code>false</code> otherwise.
     */
    private boolean verifyValidationServiceFail(List<Service<?>> services, SortedMap<Integer, ?> outcome) {
        if (outcome.isEmpty()) {
            return false;
        }
        Set<Integer> keySet = outcome.keySet();
        for (Integer integer : keySet) {
            if (!services.get(integer).getAction().validar()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Verifies given an outcome map of errors, if it's necessary to abort the whole test.
     * 
     * @param services a list of services.
     * @param outcome map of services to errors.
     * @param params params
     * @return <code>true</code> if integration test need to be aborted, <code>false</code> otherwise.
     */
    protected boolean isAbortExecution(List<Service<?>> services, SortedMap<Integer, ?> outcome, ExecutorParams params) {
        return !params.isContinueAfterFailure() && this.verifyValidationServiceFail(services, outcome);
    }

    /**
     * Executes the list of services and returns the outcome. The outcome is a map where the key/value pairs are the index and exception
     * that occurred during the service execution. An empty map means there were no exceptions.
     * 
     * @param services services
     * @return outcome
     */
    protected SortedMap<Integer, Throwable> doExecuteServices(List<Service<?>> services) {
        SortedMap<Integer, Throwable> outcome = new TreeMap<Integer, Throwable>();
        ListIterator<Service<?>> it = services.listIterator();
        while (it.hasNext()) {
            Service<?> service = it.next();
            try {
                this.executeService(services, service);
                this.executionLogController.getSessionFactory().getCurrentSession().flush();
                this.executionLogController.getSessionFactory().getCurrentSession().clear();
            } catch (ServiceValidationExecutionException e) {
                outcome.put(it.previousIndex(), e);
            } catch (ConstraintViolationException e) {
                outcome.put(it.previousIndex(), e);
            } catch (ServiceExecutionException e) {
                outcome.put(it.previousIndex(), e);
                if (!service.getAction().validar()) {
                    return outcome;
                }
            }
        }
        return outcome;
    }

    /**
     * Executes a service. If and exception is thrown during execution, all previous services will be logged as NOT_OK due to this service's
     * failure. The logging of this service is delegated to the ServiceErrorHandler.
     * 
     * @param services the services.
     * @param service service
     * @throws ServiceExecutionException if an exception (or anything throwable) is thrown during execution
     */
    protected void executeService(List<Service<?>> services, Service<?> service) throws ServiceExecutionException {
        try {
            LOG.debug(service.toString());
            this.getTimer().start(service.getServiceName());
            service.execute();
        } catch (ServiceExecutionException e) {
            LOG.error(e.getMessage(), e);
            this.getServicesController().fireServiceError(service, e);
            throw e;
        } catch (ServiceValidationExecutionException e) {
            LOG.error(e.getMessage(), e);
            this.getServicesController().fireServiceFailure(service, e);
            throw e;
        } catch (ServiceAssertionFailedError e) {
            LOG.error(e.getMessage(), e);
            this.getServicesController().fireServiceFailure(service, e);
            throw new ServiceExecutionException(service, e);
        } catch (ValidationException e) {
            LOG.error(e.getMessage(), e);
            ServiceExecutionException exception =
                    new ServiceExecutionException(
                            ServicesAPIMessages.VALIDATION_VALIDATION_EXCEPTION.create(e.getLocalizedMessageHolder()), e);
            this.getServicesController().fireServiceError(service, exception);
            throw exception;
        } catch (Throwable t) {
            LOG.error(t.getMessage(), t);
            this.getServicesController().fireServiceThrowable(service, t);
            throw new ServiceExecutionException(service, t);
        } finally {
            this.getTimer().stop(service.getServiceName());
        }
    }

    /**
     * Create a rollback log for successful executed services or a successful execution log if it's a validation service.
     * 
     * @param service the successful executed service.
     */
    private void logRollbackSuccessfulExecution(Service service) {
        if (service.getAction().validar()) {
            this.logSuccessfulExecution(service);
        } else {
            this.logNotExecuted(service, MSG_ROLLEDBACK);
        }
    }

    /**
     * Logs a successful service execution
     * 
     * @param service service que foi executado com sucesso
     */
    protected final void logSuccessfulExecution(Service<?> service) {
        if (this.executionLogController.isExecuting()) {
            ServiceBean serviceBean = new ServiceBean();
            serviceBean.setAction(service.getAction());
            serviceBean.setName(service.getServiceName());
            serviceBean.setServiceLocation(service.getServiceLocation());
            serviceBean.setParameters(service.getServiceProperties().getProperties());
            if (service.getAction().incluir()) {
                this.getExecutionLogController().addLogItem(serviceBean.toXml(), "", ExecutionItemStatus.INSERT);
            } else if (service.getAction().alterar()) {
                this.getExecutionLogController().addLogItem(serviceBean.toXml(), "", ExecutionItemStatus.UPDATE);
            } else {
                this.getExecutionLogController().addLogItem(serviceBean.toXml(), "", ExecutionItemStatus.OK);
            }
        }
    }

    /**
     * Logs a service that was executed but had errors.
     * 
     * @param service service
     * @param msg message
     * @param t the error ocurred
     */
    protected final void logErrorExecution(Service<?> service, String msg, Throwable t) {
        if (this.executionLogController.isExecuting()) {
            ServiceBean serviceBean = new ServiceBean();
            serviceBean.setAction(service.getAction());
            serviceBean.setName(service.getServiceName());
            serviceBean.setServiceLocation(service.getServiceLocation());
            serviceBean.setParameters(service.getServiceProperties().getProperties());

            Throwable t2 = t;
            if (t2 == null) {
                t2 = new Exception() {
                    @Override
                    public void printStackTrace(PrintWriter s) {
                        // empty stack
                    }
                };
            }

            this.getExecutionLogController().addErrorItem(serviceBean.toXml(), msg, t2);
        }
    }

    /**
     * Logs a service that wasn't executed, due to previous service failure.
     * 
     * @param service service
     * @param msg message
     */
    protected final void logNotExecuted(Service<?> service, String msg) {
        if (this.executionLogController.isExecuting()) {
            ServiceBean serviceBean = new ServiceBean();
            serviceBean.setAction(service.getAction());
            serviceBean.setName(service.getServiceName());
            serviceBean.setServiceLocation(service.getServiceLocation());
            serviceBean.setParameters(service.getServiceProperties().getProperties());

            this.getExecutionLogController().addNotExecutedItem(serviceBean.toXml(), msg);
        }
    }

    /**
     * @return the timer to be used to monitor services execution
     */
    protected final TimerGroup getTimer() {
        return DebugRuntimeFactory.getInstance().getOrCreateTimer(SERVICES_TIMER_IDENTIFIER);
    }

    /**
     * @return the executionLogController
     */
    public ExecutionLogController getExecutionLogController() {
        return this.executionLogController;
    }

    /**
     * @param executionLogController the executionLogController to set
     */
    public void setExecutionLogController(ExecutionLogController executionLogController) {
        this.executionLogController = executionLogController;
    }

    /**
     * @return the servicesController
     */
    public ServicesController getServicesController() {
        return this.servicesController;
    }

    /**
     * @param servicesController the servicesController to set
     */
    public void setServicesController(ServicesController servicesController) {
        this.servicesController = servicesController;
    }

    /**
     * @param rtmController the rtmController to set
     */
    public void setRtmController(RtmController rtmController) {
        this.rtmController = rtmController;
    }

    /**
     * Stores the outcome of a service's validation
     * 
     * @author lundberg
     */
    private final class ValidationOutcome {
        private final String error;

        private final Throwable exception;

        /**
         * @param error error
         * @param exception exception
         */
        private ValidationOutcome(String error, Throwable exception) {
            super();
            this.error = error;
            this.exception = exception;
        }

        /**
         * @return the error
         */
        public String getError() {
            return this.error;
        }

        /**
         * @return the exception
         */
        public Throwable getException() {
            return this.exception;
        }
    }
}
